// Package position provides all necessary functionality to deal with chess positions.
package position

import (
	b "code.google.com/p/gochess/board"
	"code.google.com/p/gochess/config"
	"fmt"
	"strings"
)

const (
	bitSizeSquareListIndex = 4
	bitMaskSquareListIndex = 1<<bitSizeSquareListIndex - 1
)

// A SquareList is an ordered collection of squares.
type SquareList struct {
	Pieces [10]b.Square
	Max    int8
}

// Appends the specified element at the end of this list
func (this *SquareList) append(square b.Square) int8 {
	this.Pieces[this.Max] = square
	this.Max++
	return this.Max - 1
}

func (this *SquareList) remove(index int8) b.Square {

	this.Max--
	this.Pieces[index] = this.Pieces[this.Max]
	return this.Pieces[index]
}

func (this *SquareList) insert(square b.Square, index int8) int8 {

	this.Pieces[this.Max] = this.Pieces[index]
	this.Pieces[index] = square
	this.Max++
	return this.Max - 1
}

func (this *SquareList) set(index int8, target b.Square) {
	this.Pieces[index] = target
}

func (this SquareList) String() string {

	var output []string
	for i := int8(0); i < this.Max; i++ {
		output = append(output, fmt.Sprintf("%d:%v", i, this.Pieces[i]))
	}
	return "[" + strings.Join(output, " ") + "]"
}

type pieceEntry struct {
	piece Piece
	index int8
}

var noPieceEntry = pieceEntry{NoPiece, 0}

func (this pieceEntry) String() string {
	return fmt.Sprintf("{%v %v}", this.piece, this.index)
}

type piecePlacement struct {
	board  [b.BiggestSquare + 1]pieceEntry
	pieces [BiggestPiece + 1]SquareList
}

// Get returns the piece on the given location.
// If location is unoccupied NoPiece is returned.
func (this *piecePlacement) Get(location b.Square) Piece {
	return this.board[location].piece
}

// SetPiece puts the given piece on the given location.
func (this *piecePlacement) Set(piece Piece, location b.Square) {

	// assert
	if config.Debug && this.board[location] != noPieceEntry {
		panic(fmt.Sprintf("b.Square %v is occupied! Can't set piece %v here! Piece placement: %v",
			location, piece, this))
	}
	this.board[location] = pieceEntry{piece, this.pieces[piece].append(location)}
}

// Remove removes a piece from the position.
// It returns the piece list index of the removed piece.
// which is used in conjunction with Reset to put back that piece on the board.
func (this *piecePlacement) Remove(location b.Square) int8 {

	pieceEntry := this.board[location]
	// assert
	if config.Debug && pieceEntry == noPieceEntry {
		panic(fmt.Sprintf("Can't remove a piece from square %v, it is empty! Piece placement: %v.", location, this))
	}
	//if config.Debug && Piece(this.board[location]&bitMaskPiece).Type() == King {
	//	panic(fmt.Sprintf("Cant remove king %v on %v!  Piece placement: %v.",
	//		Piece(this.board[location]&bitMaskPiece), location, this))
	//}
	swappedLocation := this.pieces[pieceEntry.piece].remove(pieceEntry.index)
	this.board[swappedLocation].index = pieceEntry.index
	this.board[location] = noPieceEntry

	return pieceEntry.index
}

// Reset puts back a removed piece on the given location.
// InsertIndex is the index returned by the Remove method.
// It is used to reinsert the piece at the right place in the piece list.
func (this *piecePlacement) Reset(piece Piece, location b.Square, insertIndex int8) {

	// assert
	if config.Debug && this.board[location] != noPieceEntry {
		panic(fmt.Sprintf("b.Square %v is occpupied! Can't reset piece %v here! Piece placement: %v, %s",
			location, piece, this, Render(this)))
	}

	this.board[location] = pieceEntry{piece, insertIndex}
	shuffledIndex := this.pieces[piece].insert(location, insertIndex)
	// set the index of the square that was moved to the end of the piece list
	// we can use the same piece here, as the shuffled piece is the same piece type as the resetted piece
	this.board[this.pieces[piece].Pieces[shuffledIndex]].index = shuffledIndex
}

// Move moves a piece from start to dst.
func (this *piecePlacement) Move(start, dst b.Square) {

	// assert
	if config.Debug {
		if this.board[start] == noPieceEntry {
			panic(fmt.Sprintf("Can't move a piece from %v to %v, as the start square is empty! Piece placement: %v.",
				start, dst, this))
		}
		if this.board[dst] != noPieceEntry {
			panic(fmt.Sprintf("Square %v is occpupied with piece %v! Can't move piece %v from %v to this square! Piece placement: %v",
				dst, this.board[dst], this.board[start], start, this))
		}
	}
	this.pieces[this.board[start].piece].set(this.board[start].index, dst)
	this.board[dst] = this.board[start]
	this.board[start] = noPieceEntry

}

// SetPieces is a convinience function which puts instances of the given piece
// on the given locations.
func (this *piecePlacement) SetPieces(piece Piece, location ...b.Square) {

	for i := range location {
		this.Set(piece, location[i])
	}
}

// King returns the location of the king for the given color.
//
// bug (npl): this is not inlined anymore
func (this *piecePlacement) King(player Color) b.Square {
	return this.pieces[NewPiece(player, King)].Pieces[0]
}

// PieceList returns a square list for for the given piece.
func (this *piecePlacement) PieceList(piece Piece) *SquareList {
	return &this.pieces[piece]
}

func (this *piecePlacement) String() string {

	var pieces = make(map[Color][]string, 2)
	for square := b.A1; square <= b.H8; square++ {
		pieceEntry := this.board[square]
		if pieceEntry != noPieceEntry && pieceEntry.piece != BorderPiece {
			pieces[pieceEntry.piece.Color()] = append(pieces[pieceEntry.piece.Color()],
				fmt.Sprintf("%v:%v", square, pieceEntry))
		}
	}
	whitePieces := fmt.Sprintf("\n   %s: [%s]", White, strings.Join(pieces[White], " "))
	blackPieces := fmt.Sprintf("\n   %s: [%s]", Black, strings.Join(pieces[Black], " "))

	s := "board:" + whitePieces + blackPieces + "\npiece lists:"
	for i := range &this.pieces {
		pieceList := &this.pieces[i]
		if pieceList.Max > 0 {
			s += fmt.Sprintf("\n   %v: %v", Piece(i), pieceList)
		}
	}
	return s
}

type CastlingOption uint32

// The available castling options. Order does matter, do not change.
//
// NoCastling represents the zero value for CastlingOption.
const (
	NoCastling CastlingOption = iota
	CastlingShort
	CastlingLong
	CastlingBoth
)

const (
	bitSizeCastlingOption = 2
	bitMaskCastlingOption = 1<<bitSizeCastlingOption - 1
)

const (
	numberOfCastlings int32 = int32(CastlingBoth) + 1
)

// todo: CastlingBoth has to go, as this is a castling right,
// not a castling type
var castlings = []string{
	NoCastling:    "No castling",
	CastlingShort: "Short castling",
	CastlingLong:  "Long castling",
	CastlingBoth:  "Both castlings",
}

func (this CastlingOption) String() string {
	return castlings[this]
}

/*
	bit 1-2: Castling Options for White
	bit 3-4: Castling Options for black
*/
// CastlingRights saves the castling rights for a position.
// This does not include the temporary loss of castling rights due to
// blocking pieces or attacked squares.
type CastlingRights uint32

const (
	// NoCastlingRights represents the zero value for CastlingRights.
	NoCastlingRights CastlingRights = 0x00

	// NumberOfCastlingRights is number of all different
	// possible values of CastlingRights.
	NumberOfCastlingRights int32 = 16
)

const (
	bitSizeCastlingRights = 2 * bitMaskCastlingOption
	bitMaskCastlingRights = 1<<bitSizeCastlingRights - 1
)

func NewCastlingRights(white, black CastlingOption) CastlingRights {
	return CastlingRights(NoCastling | white | (black << bitSizeCastlingOption))
}

func (this CastlingRights) Possible(player Color, option CastlingOption) bool {
	return this&CastlingRights(option<<((player-1)<<(bitSizeCastlingOption-1))) != 0
}

func (this CastlingRights) Remove(player Color, option CastlingOption) CastlingRights {
	return this &^ CastlingRights(option<<uint((player-1)<<(bitSizeCastlingOption-1)))
}

func (this CastlingRights) String() string {
	return fmt.Sprintf("White: %v, Black: %v",
		CastlingOption(this&bitMaskCastlingOption),
		CastlingOption(this>>bitSizeCastlingOption))
}

const (
	bitSizeHalfmoveClock = 8
	bitMaskHalfmoveClock = 1<<bitSizeHalfmoveClock - 1
)

// Position represents a chess position.
type Position struct {
	piecePlacement

	// who is in turn?
	sideToMove Color

	// after a pawn double push this is a possible capture square for an opposite pawn
	epTarget b.Square

	// enforcing fifty move rule
	// beware, its 50 moves, not 50 ply!
	halfmoveClock int32

	castlingRights CastlingRights
}

// NewPosition creates a position for the given parameters and
// returns its address.
func NewPosition(stm Color, epTarget b.Square,
	white, black CastlingOption, halfmoveClock int32) *Position {

	p := &Position{sideToMove: stm, epTarget: epTarget,
		halfmoveClock: halfmoveClock, castlingRights: NewCastlingRights(white, black)}

	for square := range p.piecePlacement.board {
		if !b.IsValid(b.Square(square)) {
			p.piecePlacement.board[square].piece = BorderPiece
		}
	}
	return p
}

// EnPassantSquare returns the current place where a pawn can capture en passant
// with the next move.
func (this *Position) EnPassantTarget() b.Square {
	return this.epTarget
}

// SetEnPassantSquare sets the location for possible en passant captures as next move.
func (this *Position) SetEnPassantTarget(new b.Square) {
	this.epTarget = new
}

func (this *Position) CastlingRights() CastlingRights {
	return this.castlingRights
}

func (this *Position) SetCastlingRights(new CastlingRights) {
	this.castlingRights = new
}

func (this *Position) HalfmoveClock() int32 {
	return this.halfmoveClock
}

func (this *Position) SetHalfmoveClock(new int32) {
	this.halfmoveClock = new
}

func (this *Position) SideToMove() Color {
	return this.sideToMove
}

// SwitchSides passes the right to move to the currently inactive player.
func (this *Position) SwitchSides() {
	this.sideToMove = Opponent(this.sideToMove)
}

// TODO: write tests for it.
//
// Is50MoveDraw  returns true if the conditions for possible draw
// by 50 move rule are met.
// stockfish:
// if (st->rule50 > 99 && !is_mate())
// return true;
func (this *Position) Is50MoveDraw() bool {
	return this.halfmoveClock > 99
}

var pieces = map[Piece]string{
	WhiteKing:   "K",
	WhiteQueen:  "Q",
	WhiteRook:   "R",
	WhiteBishop: "B",
	WhiteKnight: "N",
	WhitePawn:   "P",
	BlackKing:   "k",
	BlackQueen:  "q",
	BlackRook:   "r",
	BlackBishop: "b",
	BlackKnight: "n",
	BlackPawn:   "p",
}

// A Type that satisfies Renderable can be printed by the Render function.
type Renderable interface {
	Get(b.Square) Piece
}

// Render returns a human readable 8x8 board representation of renderable position.
func Render(p Renderable) string {

	output := "\n"
	for rank := b.Rank8; rank >= b.Rank1; rank-- {
		for file := b.A; file <= b.H; file++ {
			square := b.GetSquare(file, rank)
			piece := p.Get(square)
			if piece == NoPiece {
				output += ". "
			} else {
				output += fmt.Sprintf("%s ", pieces[piece])
			}
		}
		output += "\n"
	}
	return output
}

func (this *Position) String() string {

	return fmt.Sprintf("%v\nCastling Rights: %v\nSide to Move: %v, En Passant Target: %v, Halfmove Clock: %v",
		&this.piecePlacement, this.castlingRights, this.sideToMove, this.epTarget, this.halfmoveClock)
}

// NewInitialPosition creates a initial chess position and returns its address.
func NewInitialPosition() *Position {
	p := NewPosition(White, b.NoSquare, CastlingBoth, CastlingBoth, 0)
	p.Set(WhiteKing, b.E1)
	p.Set(WhiteQueen, b.D1)
	p.SetPieces(WhiteRook, b.A1, b.H1)
	p.SetPieces(WhiteBishop, b.C1, b.F1)
	p.SetPieces(WhiteKnight, b.B1, b.G1)
	p.SetPieces(WhitePawn, b.A2, b.B2, b.C2, b.D2, b.E2, b.F2, b.G2, b.H2)

	p.Set(BlackKing, b.E8)
	p.Set(BlackQueen, b.D8)
	p.SetPieces(BlackRook, b.A8, b.H8)
	p.SetPieces(BlackBishop, b.C8, b.F8)
	p.SetPieces(BlackKnight, b.B8, b.G8)
	p.SetPieces(BlackPawn, b.A7, b.B7, b.C7, b.D7, b.E7, b.F7, b.G7, b.H7)

	return p
}

func NewMidgamePosition() *Position {

	p := NewPosition(White, b.NoSquare, NoCastling, NoCastling, 0)

	p.Set(WhiteKing, b.C1)
	p.Set(WhiteQueen, b.F3)
	p.SetPieces(WhiteRook, b.D1, b.H1)
	p.SetPieces(WhiteBishop, b.D3, b.D4)
	p.Set(WhiteKnight, b.C3)
	p.SetPieces(WhitePawn, b.A2, b.B2, b.C2, b.E4, b.F4, b.G5, b.H2)

	p.Set(BlackKing, b.G8)
	p.Set(BlackQueen, b.B8)
	p.SetPieces(BlackRook, b.A8, b.F8)
	p.SetPieces(BlackBishop, b.C8, b.E7)
	p.Set(BlackKnight, b.D7)
	p.SetPieces(BlackPawn, b.A6, b.B4, b.D6, b.E6, b.F7, b.G7, b.H7)

	return p
}

func NewEndgamePosition() *Position {

	p := NewPosition(White, b.NoSquare, NoCastling, NoCastling, 0)
	p.Set(WhiteKing, b.G2)
	p.Set(WhiteQueen, b.E1)
	p.Set(WhiteRook, b.B1)
	p.SetPieces(WhitePawn, b.E4, b.F2, b.G4, b.H3)

	p.Set(BlackKing, b.C7)
	p.Set(BlackQueen, b.D3)
	p.Set(BlackRook, b.E5)
	p.SetPieces(BlackPawn, b.C6, b.D6, b.F6, b.G7, b.G5)

	return p
}

// NewFine70Position creates the legendary "Fine #70" position
// (also known as Laster-Reichhelm Position) and returns its address.
// (see http://chessprogramming.wikispaces.com/Lasker-Reichhelm+Position)
//
// This is an excellent positions for finding bugs.
func NewFine70Position() *Position {

	p := NewPosition(White, b.NoSquare, NoCastling, NoCastling, 0)
	p.Set(WhiteKing, b.A1)
	p.SetPieces(WhitePawn, b.A4, b.D4, b.D5, b.F4)
	p.Set(BlackKing, b.A7)
	p.SetPieces(BlackPawn, b.A5, b.D6, b.F5)

	return p
}
